// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: leon

#pragma once

#include <string>

#include <rapidjson/document.h>
#include <rapidjson/rapidjson.h>

#include "coin/proto/coin_market_enums.pb.h"
#include "coin2/base/conversion.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/feed_util/book_builder.h"
#include "coin2/exchange/base/feed_util/trade.h"

namespace impl {
namespace {
const int kConsecutiveZeroSizeThreshold = 3;
// Bitflyer periodic maintenance happens from 19:00 UTC to 19:10 UTC.
bool IsDuringMaintenance(int64_t timestamp) {
  int epoch_seconds = timestamp / 1e9;
  int hour = (epoch_seconds / 3600) % 24;
  int minute = (epoch_seconds / 60) % 60;
  if (hour == 19 && minute >= 0 && minute <= 10) {
    return true;
  } else {
    return false;
  }
}
bool during_maintenance = false;
}  // namespace
using coin2::exchange::base::market::ExchangeType;
using coin2::exchange::base::market::MarketType;

class BitflyerFuturesFeedUpdateParser {
 public:
  static bool
  ParseSnapshot(BookBuilder* book_builder_, const rapidjson::Value& book_json, int64_t timestamp) {
    auto tx = book_builder_->NewTransaction(timestamp);

    for (const auto& ask : book_json["asks"].GetArray()) {
      double price = stod(ask["price"].GetString());
      double size = stod(ask["size"].GetString());
      // https://bf-lightning-api.readme.io/docs/realtime-board
      // If a market order is executed at the time of Itayose,
      // an update will be sent with price: 0.
      if (price == 0) {
        continue;
      }
      tx.UpdateAsk(price, size);
    }

    for (const auto& bid : book_json["bids"].GetArray()) {
      double price = stod(bid["price"].GetString());
      double size = stod(bid["size"].GetString());
      if (price == 0) {
        continue;
      }
      tx.UpdateBid(price, size);
    }

    if (book_builder_->AskSize() == 0 || book_builder_->BidSize() == 0) {
      tx.Clear();
      return false;
    }
    return true;
  }

  static bool
  ParseUpdate(BookBuilder* book_builder_, const rapidjson::Value& book_json, int64_t timestamp) {
    auto tx = book_builder_->NewTransaction(timestamp);

    int consecutive_zero_size = 0;
    if (book_json.HasMember("asks")) {
      for (const auto& ask : book_json["asks"].GetArray()) {
        double price = stod(ask["price"].GetString());
        double size = stod(ask["size"].GetString());
        // https://bf-lightning-api.readme.io/docs/realtime-board
        // If a market order is executed at the time of Itayose,
        // an update will be sent with price: 0.
        if (price == 0) {
          continue;
        }
        if (size == 0 && consecutive_zero_size >= 0) {
          ++consecutive_zero_size;
        } else {
          consecutive_zero_size = -1;
        }
        tx.UpdateAsk(price, size);
      }
    }

    if (book_json.HasMember("bids")) {
      for (const auto& bid : book_json["bids"].GetArray()) {
        double price = stod(bid["price"].GetString());
        double size = stod(bid["size"].GetString());
        if (price == 0) {
          continue;
        }
        if (size == 0 && consecutive_zero_size >= 0) {
          ++consecutive_zero_size;
        } else {
          consecutive_zero_size = -1;
        }
        tx.UpdateBid(price, size);
      }
    }

    if (book_builder_->AskSize() == 0 || book_builder_->BidSize() == 0) {
      tx.Clear();
      return false;
    }

    if (!during_maintenance && consecutive_zero_size >= kConsecutiveZeroSizeThreshold &&
        IsDuringMaintenance(timestamp)) {
      during_maintenance = true;
      tx.Clear();
      return false;
    } else if (during_maintenance) {
      if (!IsDuringMaintenance(timestamp)) {
        during_maintenance = false;
      }
      tx.Clear();
      return false;
    }
    return true;
  }

  static void ParseTrade(const rapidjson::Value& trade_json, Trade* trade) {
    trade->price = stod(trade_json["price"].GetString());
    trade->fill_qty = stod(trade_json["size"].GetString());
    trade->side = ParseTradeSide(trade_json["side"].GetString());
    trade->timestamp = TimestampFromIso8601(trade_json["exec_date"].GetString());
  }

  static coin2::exchange::feed::TradeSide ParseTradeSide(const std::string& side) {
    if (side == "BUY") {
      return coin2::exchange::feed::TRADE_BUY_SIDE;
    } else if (side == "SELL") {
      return coin2::exchange::feed::TRADE_SELL_SIDE;
    } else if (side.empty()) {
      return coin2::exchange::feed::TRADE_NO_SIDE;
    } else {
      throw std::runtime_error("[Bitflyer Futures] unknown side: " + side);
    }
  }
};
}  // namespace impl

using impl::BitflyerFuturesFeedUpdateParser;
